跳到主要内容

Go GORM v2 笔记

这篇笔记主要是 GORM v2 的使用

gorm 框架

gorm 是 Golang 语言中的一款 ORM 库,具体使用方法官网文档已经写了,具体使用方式还是看 官方文档,这里主要记录些平时用到的部分,方便开发时直接复制粘贴

# 注意:jinzhu/gorm 是旧版的,参考这个: https://gorm.io/zh_CN/docs/v2_release_note.html

go get -u gorm.io/gorm

# go get -u gorm.io/driver/mysql
go get -u gorm.io/driver/sqlite

连接数据库 ⭐

使用示例

import (
"database/sql"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"log"
"os"
"time"
)

var db *gorm.DB

func init() {
var err error

db, err = gorm.Open(mysql.New(mysql.Config{
// DSN data source name
DSN: fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",
"root", "root", "127.0.0.1:3306", "blog"),
DefaultStringSize: 256, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{
// 在 gorm v2 中设置 表前缀 是在 Config 中配置的
NamingStrategy: schema.NamingStrategy{TablePrefix: "tb_", SingularTable: true},
Logger: logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: false, // 禁用彩色打印
},
),
})

if err != nil {
log.Println(err)
}

// 配置数据库连接池
sqlDB, err := db.DB()
// defer CloseDB(sqlDB)
// 设置空闲连接池中连接的最大数量
sqlDB.SetMaxIdleConns(10)
// 设置打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)
// 设置了连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)
}


// 其实不需要手动关闭连接池
func CloseDB(sqlDB *sql.DB) {
err := sqlDB.Close()
if err != nil {
log.Fatal(2, "Fail to close 'database': %v", err)
}
}

这个 sql.DB 的使用参考 常规数据库接口 sql.DB

快速使用

按照官网的示例代码运行:

func main() {
// 创建表或自动迁移(把结构体和数据表进行对应)
err := db.AutoMigrate(&Auth{})
if err != nil {
panic(err)
return
}

// ====================(增加)========================
// 创建数据行
au1 := &Auth{}
au1.Username = "李四"
au1.Password = "123456"
// 也可以下面这样创建
//au1 := &Auth{gorm.Model{ID: 2, CreatedAt: time.Now(), UpdatedAt: time.Now()}, "张三", "123456"}
db.Create(au1)

var au2 Auth
var aus []Auth
// ====================(查询)========================
// 查询数据,获取第一条记录(主键升序)
db.First(&au2)
// SELECT * FROM `blog_auth` WHERE `blog_auth`.`deleted_at` IS NULL
// ORDER BY `blog_auth`.`id` LIMIT 1
log.Printf("%v \n", au2)

// 获取第一条匹配的记录
db.Where("username = ?", "test").First(&au2)
// SELECT * FROM `blog_auth` WHERE username = 'test'
// AND `blog_auth`.`deleted_at` IS NULL
// AND `blog_auth`.`id` = 1
// ORDER BY `blog_auth`.`id` LIMIT 1
log.Printf("%v \n", au2)

// 获取全部匹配的记录
db.Where("username != ?", "test").Find(&aus)
// SELECT * FROM `blog_auth` WHERE username != 'test' AND `blog_auth`.`deleted_at` IS NULL
log.Printf("%v \n", aus)

// ====================(修改)========================
// 更新数据
db.Model(&au2).Where("username = ?", "test").Update("password", "123456")
// 等价于
// UPDATE `blog_auth` SET `password`='123456',`updated_at`='2021-11-02 23:21:01.052'
// WHERE username = 'test' AND `blog_auth`.`deleted_at` IS NULL AND `id` = 1
}

这里的 Automigrate() 操作,其作用主要是刷新数据库中的表格,使其保持最新,即让数据库之前存储的记录的表格字段和程序中最新使用的表格字段保持一致(只增不减)。

其实一般只有测试环境会使用这个 Automigrate() 操作

自动迁移 (操作表结构)

在项目开发中,我们可能会随时调整声明的模型,比如添加字段和索引,使用 GORM 的自动迁移功能,可以始终让我们的数据库表保持最新。提供的 Automigrate() 函数,其作用主要是刷新数据库中的表格,使其保持最新,即让数据库之前存储的记录的表格字段和程序中最新使用的表格字段保持一致(只增不减)。

db.AutoMigrate(&User{})

db.AutoMigrate(&User{}, &Product{}, &Order{})

// 创建表时添加后缀
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})

注意 AutoMigrate 会自动创建数据库外键约束,可以在初始化时禁用此功能

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})

指定表前缀

在 v1 时可以设置表前缀

gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string  {
return tablePrefix + defaultTableName;
}

在 gorm v2 中设置 表前缀 是在 Config 中配置的,具体参考 GORM 配置

db, err := gorm.Open(mysql.New(mysql.Config{
// DSN data source name
DSN: fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",
user, password, host, dbName),
DefaultStringSize: 256, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{
// 在 gorm v2 中设置 表前缀 是在 Config 中配置的
NamingStrategy: schema.NamingStrategy{TablePrefix: tablePrefix, SingularTable: true},
})

回调函数

如下,可以在执行某些操作之前执行

type Tag struct {
Model
Name string `json:"name"`
CreatedBy string `json:"created_by"` // 创建人
ModifiedBy string `json:"modified_by"` // 修改人
State int `json:"state"`
}

func (tag *Tag) BeforeCreate(tx *gorm.DB) error {
tag.CreatedOn = int(time.Now().Unix())
return nil
}

func (tag *Tag) BeforeUpdate(tx *gorm.DB) error {
tag.ModifiedOn = int(time.Now().Unix())
return nil
}

实际上它实现了 BeforeCreateInterface 接口

type BeforeCreateInterface interface {
BeforeCreate(*gorm.DB) error
}

具体参考 官方文档 Hook

事务

要在事务中执行一系列操作,一般流程如下:

db.Transaction(func(tx *gorm.DB) error {
// 在事务中执行一些 db 操作(从这里开始,应该使用 'tx' 而不是 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// 返回任何错误都会回滚事务
return err
}

if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}

// 返回 nil 提交事务
return nil
})

Reference

Go gorm document cn Go中使用 SQLite 数据库(Gorm)